/** ScreenManager.cs - A simple Game Screen Manager
 * @author - Spencer Evans
 * @attribution - Similar to a class found in the Game State Management sample on the 
 *  XNA Creators Club Online website (http://creators.xna.com).
 */


#region Using Statements
// .Net
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Text;

// XNA
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
#endregion


namespace BenevolentSun
{
    /** The ScreenManager manages a stack of game screens. 
     * It intelligently updates and draws appropriate screens and
     * routes input handling to the topmost active screen.
     */
    public class ScreenManager : DrawableGameComponent
    {
        #region Fields
        // Fields
        /** The actual list of screens, treated as a stack. Last-In-First-Updated/Drawn/Input Actived. */
        List<Screen> screens = new List<Screen>();
        /** The list of screens to be updated each Game Update. Just a copy of screens to avoid screen adding/removal conflicts. */
        List<Screen> screensToUpdate = new List<Screen>();
        
        /** Keeps track of if and when the Manager has been initialized - mainly a flag for if we have a graphics device */
        bool isInitialized = false;
        #endregion



        #region Properties
        /** Spritebatch used to render. */
        SpriteBatch spriteBatch;
        public SpriteBatch SpriteBatch
        {
            get { return spriteBatch; }
        }

        public Vector2 ScreenCenter
        {
            get { return new Vector2(GraphicsDevice.Viewport.Width / 2.0f, GraphicsDevice.Viewport.Height / 2.0f); }
        }

        public Vector2 ScreenSize
        {
            get { return new Vector2(GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height); }
        }
        #endregion



        #region Initialization
        // Constructors
        public ScreenManager(Game game)
            : base(game)
        {
        }

        // Initializers
        public override void Initialize()
        {
            base.Initialize();
            isInitialized = true;
        }
        /** Load graphics content for this manager and each game screen.
         */
        protected override void LoadContent()
        {
            // Load content belonging to the screen manager.
            ContentManager content = Game.Content;

            spriteBatch = new SpriteBatch(GraphicsDevice);

            // Tell each of the screens to load their content.
            foreach (Screen screen in screens)
            {
                screen.LoadContent();
            }
        }

        // Deinitializers
        /** Unload graphics content for each of the game screens.
         */
        protected override void UnloadContent()
        {
            // Tell each of the screens to unload their content.
            foreach (Screen screen in screens)
            {
                screen.UnloadContent();
            }

            // TODO: Should we be unloading the managers content here?
        }
        #endregion



        #region Update Methods
        // Update and Draw
        /** Updates all game screens and lets the topmost active one have input focus.
         */
        public override void Update(GameTime gameTime)
        {
            // Rebuild the screens to update list to avoid adding and removal conflicts
            screensToUpdate.Clear();
            foreach (Screen screen in screens)
                screensToUpdate.Add(screen);

            // Keep track of when the focus is stolen and full layer occlusions
            bool isFocusTaken = !Game.IsActive;     // if the game is inactive, nothing can have focus
            bool isOccluded = false;                // top most window will never be occluded by anything

            // Update each screen starting at the top of the stack
            while (screensToUpdate.Count > 0)
            {
                // Pop off the topmost screen
                Screen screen = screensToUpdate[screensToUpdate.Count - 1];
                screensToUpdate.RemoveAt(screensToUpdate.Count - 1);

                // Update the screen
                screen.Update(gameTime, !isFocusTaken, isOccluded);

                // If the screen is on or turning on check to see if it gets focus or occludes things
                if (screen.ScreenState == ScreenState.TransitionOn || screen.ScreenState == ScreenState.Active)
                {
                    // If focus is not yet taken, allow this screen to take it
                    if (!isFocusTaken)
                    {
                        if (screen.IsModal)
                            isFocusTaken = true;
                        screen.HandleInput();
                    }

                    // If this screen is full screen (not a popup, note that it will occlude lower level screens
                    if (!screen.IsPopup)
                        isOccluded = true;
                }
            }
        }
        #endregion



        #region Draw Methods
        /** Draws all non hidden game screens.
         */
        public override void Draw(GameTime gameTime)
        {
            // Allow each non hidden screen to draw
            foreach (Screen screen in screens)
            {
                if (screen.ScreenState == ScreenState.Hidden)
                    continue;

                screen.Draw(gameTime);
            }
        }
        #endregion



        #region Public Methods
        // Stack Manipulation Methods
        /** Adds a screen to the stack.
         */
        public void AddScreen(Screen screen)
        {
            // Setup the screen to be managed
            screen.ScreenManager = this;
            screen.IsExiting = false;

            // Load the screens content if the manager has already been initialized
            if (isInitialized)
            {
                screen.LoadContent();
            }

            // Add it to the screen list
            screens.Add(screen);
        }
        /** Instantly removes a Screen without allowing it to transition, normally use Screen.ExitScreen.
         */
        public void RemoveScreen(Screen screen)
        {
            // Instantly remove the screen, even from the current update
            screens.Remove(screen);
            screensToUpdate.Remove(screen);

            // Deinitialize the screen
            if (isInitialized)
            {
                screen.UnloadContent();
            }
        }
        /** Returns a copy of the screen list as an array. Use the add and remove methods to manipulate the stack.
         */
        public Screen[] GetScreens()
        {
            return screens.ToArray();
        }
        #endregion
    }
}
